diff options
| author | Adam Soltys <[email protected]> | 2022-08-16 15:31:41 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-08-16 15:31:41 -0700 |
| commit | 770725fbdcc8ea7bf47ef0af5a43a239bef98ff6 (patch) | |
| tree | 445d55b896a63eab5a29204e720a9e74d0629697 /src/routes/invoice/[id] | |
| parent | 0b51e1dfd0d099c243d21889fb77fea4769dac0a (diff) | |
| download | coinos-ui-770725fbdcc8ea7bf47ef0af5a43a239bef98ff6.tar.gz coinos-ui-770725fbdcc8ea7bf47ef0af5a43a239bef98ff6.zip | |
sveltekit route migration
Diffstat (limited to 'src/routes/invoice/[id]')
| -rw-r--r-- | src/routes/invoice/[id]/+page.server.js | 14 | ||||
| -rw-r--r-- | src/routes/invoice/[id]/+page.svelte | 351 |
2 files changed, 365 insertions, 0 deletions
diff --git a/src/routes/invoice/[id]/+page.server.js b/src/routes/invoice/[id]/+page.server.js new file mode 100644 index 00000000..78607545 --- /dev/null +++ b/src/routes/invoice/[id]/+page.server.js @@ -0,0 +1,14 @@ +import { get } from '$lib/utils'; + +export async function load({ params }) { + let { id } = params; + let { + amount, + rate, + status, + text, + user: { username } + } = await get(`/invoice?uuid=${id}`); + + return { amount, id, rate, username, status, text }; +} diff --git a/src/routes/invoice/[id]/+page.svelte b/src/routes/invoice/[id]/+page.svelte new file mode 100644 index 00000000..0252a6b1 --- /dev/null +++ b/src/routes/invoice/[id]/+page.svelte @@ -0,0 +1,351 @@ +<script> + throw new Error("@migration task: Add data prop (https://github.com/sveltejs/kit/discussions/5774#discussioncomment-3292707)"); + + import { get, post, reverseFormat } from '$lib/utils'; + import { browser } from '$app/env'; + import { invoices, user, preferredCurrency } from '$lib/store'; + import { Icon, Image } from '$comp'; + import { goto } from '$app/navigation'; + import { onMount } from 'svelte'; + import { copy } from '$lib/utils'; + + export let amount, id, rate, status, text, username; + + $invoices[id] = { amount, id, rate, status, text, username }; + + $: amountFiat = parseFloat(((amount * rate) / 100000000).toFixed(2)); + + let tipPercent = 0; + let qr; + + $: tipAmount = customTipAmount + ? customTipAmount < 0 + ? (customTipAmount = 0) + : customTipAmount + : (amountFiat / 100) * tipPercent; + + $: tipAmountFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(tipAmount); + + $: tipAmountSats = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + tipAmount / (rate / 100000000) + ); + + $: totalAmountSats = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + amount + parseFloat(tipAmountSats.replace(/,/g, '')) + ); + + $: update(tipAmountSats); + let update = async (n) => { + let { id } = await post('/invoice', { + amount: amount + reverseFormat(n), + rate, + text, + username + }); + ({ text } = await get(`/invoice/${id}`)); + }; + + $: totalAmountFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amountFiat + parseFloat(tipAmount)); + + const amountFormatted = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 0 + }).format(amount); + + $: invoiceAmountFiatFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amountFiat); + + let customTipAmount; + + const tipAmounts = ['None', '10%', '15%', '20%']; + + const handleTipButtonClick = (amount) => { + customTipAmount = ''; + customInput.value = ''; + + if (amount === 'None') { + tipPercent = 0; + } else { + tipPercent = amount.slice(0, 2); + } + }; + + const handleCustomTipAmount = (e) => { + tipPercent = 0; + customTipAmount = e.target.value; + }; + + $: generate(text); + let generate = async (ready) => { + if (!ready) return; + let { default: QRCodeStyling } = await import('qr-code-styling'); + + const qrCode = new QRCodeStyling({ + width: window.screen.width < 640 ? 250 : 300, + type: 'canvas', + data: text, + image: '/images/invoice.svg', + backgroundOptions: { + color: 'rgba(0, 0, 0, 0)' + }, + dotsOptions: { + type: 'rounded' + }, + imageOptions: { + hideBackgroundDots: false + } + }); + + qrCode.append(qr); + }; + + let showMobileTip = false; + + const handleBackButton = () => { + if (showMobileTip) { + tipPercent = 0; + customInput.value = ''; + customTipAmount = 0; + tipAmount = 0; + showMobileTip = false; + } else { + goto(`/${$user.username}/receive`); + } + }; + + let customInput; + + const handleDoneClick = () => { + if ($user) { + goto(`/${$user.username}/receive`); + } else { + goto('/'); + } + }; +</script> + +{#if $invoices[id]?.status !== 'paid'} + <div class:full-shadow={showMobileTip}> + <button + class="ml-5 md:ml-20 mt-5 md:mt-10" + class:invisible={!$user} + on:click={handleBackButton} + > + <Icon icon="arrow-left" style="w-10" /> + </button> + + <div class="flex justify-center items-center mb-20 md:mt-[20px] px-3"> + <div + class="block lg:flex justify-center items-center space-y-10 lg:space-y-0 space-x-0 lg:space-x-24" + > + <!-- tipping section --> + <div + class="w-[100%] md:w-auto absolute {showMobileTip + ? 'bottom-0' + : '-top-full'} bg-white p-6 md:p-0 rounded-t-3xl md:rounded-none left-0 md:static text-center space-y-5" + > + <h1 class="hidden md:block text-4xl font-semibold">Add a tip?</h1> + + <div> + <span class="font-semibold mr-1">{tipAmountFormatted}</span><span + class="text-secondary font-semibold text-sm">{`(${tipAmountSats} SATS)`}</span + > + <span class="block text-secondary text-sm">{tipPercent}%</span> + </div> + + <input + id="slider" + type="range" + min="0" + max="100" + value={tipPercent} + class="px-2" + on:input={(e) => { + customTipAmount = ''; + customInput.value = ''; + tipPercent = e.target.value; + }} + /> + + <div> + {#each tipAmounts as amount} + <button + class="bg-primary w-16 md:w-20 py-3 m-1 rounded-2xl font-semibold {customTipAmount + ? '' + : tipPercent === amount.slice(0, 2) || + ((tipPercent === 0 || tipPercent === '0') && amount === 'None') + ? '!bg-black text-white' + : ''}" + on:click={() => handleTipButtonClick(amount)}>{amount}</button + > + {/each} + + <div class="relative"> + <span + class="absolute top-[20px] left-5 font-semibold {!customTipAmount + ? 'opacity-50' + : 'opacity-100'}">$</span + > + <input + bind:this={customInput} + type="number" + min="0" + on:input={(e) => handleCustomTipAmount(e)} + class="bg-primary rounded-2xl py-3 px-10 font-semibold mt-2 mr-1 w-full md:w-auto" + placeholder="Other" + /> + <button + class="w-full md:w-auto mt-2 md:mt-0 bg-black text-white font-semibold py-3 px-7 rounded-2xl {showMobileTip + ? !customTipAmount && !tipAmount + ? 'opacity-50' + : 'opacity-100' + : !customTipAmount + ? 'opacity-50' + : 'opacity-100'}" + disabled={showMobileTip ? !customTipAmount && !tipAmount : !customTipAmount} + on:click={() => (showMobileTip = false)}>Done</button + > + </div> + </div> + </div> + + <div + class="hidden md:block h-[1px] lg:h-[600px] w-full lg:w-[1px] bg-lightgrey rounded-xl" + id="section-divider" + /> + + <!-- invoice section --> + <div class="w-[325px] text-center space-y-5"> + <button + class="block md:hidden bg-black {showMobileTip + ? 'text-white/50' + : 'text-white'} rounded-xl px-6 py-3 block text-sm font-semibold mx-auto flex justify-center items-center" + on:click={() => (showMobileTip = true)} + disabled={showMobileTip} + > + <svg + width="24" + height="24" + class="mr-1" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M4.31802 6.31802C2.56066 8.07538 2.56066 10.9246 4.31802 12.682L12.0001 20.364L19.682 12.682C21.4393 10.9246 21.4393 8.07538 19.682 6.31802C17.9246 4.56066 15.0754 4.56066 13.318 6.31802L12.0001 7.63609L10.682 6.31802C8.92462 4.56066 6.07538 4.56066 4.31802 6.31802Z" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> Add a tip + </button> + + <span class="text-secondary block" + >Scan to pay <a href="/{username}" class="text-black font-semibold">{username}</a></span + > + + <div + bind:this={qr} + on:click={() => copy(text)} + class="border {showMobileTip + ? 'border-gray-400' + : 'border-lightgrey'} px-5 md:px-0 w-[292px] h-[302px] md:w-[300px] md:h-[300px] rounded-3xl block md:flex justify-center items-center cursor-pointer mx-auto" + /> + + <div class="px-5 space-y-3"> + <div class="flex justify-between"> + <span class="font-semibold text-sm">Invoice</span> + <span class="font-semibold text-sm" + >{invoiceAmountFiatFormatted} + <span class="text-secondary font-normal">{`(${amountFormatted} SATS)`}</span></span + > + </div> + + <div class="flex justify-between"> + <span class="font-semibold text-sm">Tip</span> + <span class="font-semibold text-sm" + >{tipAmountFormatted} + <span class="text-secondary font-normal">{`(${tipAmountSats} SATS)`}</span></span + > + </div> + + <div class="flex flex-wrap justify-between"> + <span class="font-bold mr-1">Total</span> + <span class="font-bold" + >{totalAmountFormatted} + <span class="text-secondary font-normal">{`(${totalAmountSats} SATS)`}</span></span + > + </div> + </div> + </div> + </div> + </div> + </div> +{:else} + <div class="text-center mt-20 md:mt-0"> + <Image image="success" style="w-full md:w-3/4 xl:w-1/2 mx-auto max-w-3xl" /> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment Successful!</h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {totalAmountFormatted} + {$preferredCurrency} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">({totalAmountSats} SATS)</h3> + <button class="bg-black text-white rounded-2xl w-20 py-3 font-bold" on:click={handleDoneClick}> + Done + </button> + </div> +{/if} + +<style> + #slider { + -webkit-appearance: none; + width: 100%; + height: 6px; + border-radius: 5px; + background: #eaecef; + outline: none; + } + + #slider:active { + background-color: black; + } + + #slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 25px; + height: 25px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + #slider::-moz-range-thumb { + width: 25px; + height: 25px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + } + + input[type='number'] { + -moz-appearance: textfield; + } + + .full-shadow { + box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.25); + } +</style> |
